import { platform } from "os";
import { request } from "https";
import { posix, resolve, relative, parse } from "path";
import { readFileSync, promises } from "fs";
import fastGlob from "fast-glob";
import svgo from "svgo";
import ansi from "ansi-colors";
const isWindows = platform() === "win32";
const slash = (p) => p.replace(/\\/g, "/");
const normalizePath = (id) => posix.normalize(isWindows ? slash(id) : id);
const randomIp = () => Array.from({ length: 4 }).fill(0).map(() => Number.parseInt(`${Math.random() * 255}`)).join(".");
const shuffleArray = (arr) => arr.sort(() => Math.random() - 0.5);
const ROOT = normalizePath(process.cwd());
const { cyan, magenta, greenBright, blueBright, gray, grey, red, green, yellow, blue, dim, bold } = ansi;
export const viteTinyfy = (options = {}) => {
    const outDir = options.outDir || "dist";
    const max = options.max || 5_242_880;
    const quiet = options.quiet ?? false;
    const pass = options.pass || false;
    const log = (str) => {
        if (quiet)
            return;
        console.log(str);
    };
    const svgConfig = Object.assign(options.svgConfig || {}, {
        multipass: true,
        plugins: [
            {
                name: "preset-default",
                params: {
                    overrides: {
                        cleanupNumericValues: false,
                        removeViewBox: false,
                    },
                },
            },
            "sortAttrs",
            {
                name: "addAttributesToSVGElement",
                params: {
                    attributes: [{ xmlns: "http://www.w3.org/2000/svg" }],
                },
            },
        ],
    });
    function applySVGO(inputPath, outPath) {
        return new Promise(async (resolve, reject) => {
            const data = readFileSync(inputPath, 'utf8');
            const stats = await promises.stat(inputPath);
            if (stats.size > max) {
                log(red(`⚠️ wei xian[file]${inputPath} --> TinyfyProcessor not support oversize ${stats.size}, expected ${max}`));
                return resolve(false);
            }
            const result = svgo.optimize(data, Object.assign({ path: inputPath }, svgConfig)).data;
            promises.writeFile(outPath, result).then(() => resolve({ path: inputPath, size: stats.size })).catch(() => reject());
        });
    }
    ;
    function fetchTingfy(file) {
        return new Promise(async (resolve, reject) => {
            const http = request({
                headers: {
                    "Cache-Control": "no-cache",
                    "Content-Type": "image/png",
                    "Postman-Token": Date.now(),
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
                    "X-Forwarded-For": randomIp(),
                },
                hostname: "tinypng.com",
                method: "POST",
                path: "/backend/opt/shrink",
                rejectUnauthorized: false,
            }, (res) => {
                const chunks = [];
                res.on("data", (chunk) => {
                    chunks.push(chunk);
                });
                res.on("end", () => {
                    const body = Buffer.concat(chunks);
                    let object;
                    try {
                        object = JSON.parse(body.toString());
                        object.error ? reject(object.message) : resolve(object);
                    }
                    catch {
                        reject(body);
                    }
                });
            });
            http.on("error", (error) => {
                reject(error);
            });
            http.write(file, "binary");
            http.end();
        });
    }
    function download(url) {
        const options = new URL(url);
        return new Promise((resolve, reject) => {
            const http = request(options, (res) => {
                let file = "";
                res.setEncoding("binary");
                res.on("data", (chunk) => {
                    file += chunk;
                });
                res.on("end", () => resolve(file));
            });
            http.on("error", (error) => {
                reject(error);
            });
            http.end();
        });
    }
    function applyTinyfy(inputPath, outPath) {
        return new Promise(async (resolve, reject) => {
            const file = await promises.readFile(inputPath, "binary");
            const stats = await promises.stat(inputPath);
            if (stats.size > max) {
                log(red(`[file]${inputPath} --> TinyfyProcessor not support oversize ${stats.size}, expected ${max}`));
                return resolve(false);
            }
            const { output = {} } = await fetchTingfy(file);
            if (!output.url) {
                log(red("TinyfyProcessor upload fail"));
                return resolve(false);
            }
            ;
            const data = await download(output.url);
            if (!data) {
                log(red("TinyfyProcessor download fail"));
                return resolve(false);
            }
            ;
            promises.writeFile(outPath, data, "binary").then(() => resolve({ path: inputPath, size: stats.size })).catch(() => reject());
        });
    }
    return {
        name: "vite-plugin-sharp",
        apply: "build",
        enforce: "post",
        async closeBundle() {
            if (pass)
                return;
            // 压缩图片
            const files = fastGlob.sync([
                "**.png",
                "**.jpg",
                "**.jpeg",
                "**.gif",
                "**.tiff",
                "**.webp",
                "**.svg",
                "**.avif",
            ], { cwd: outDir });
            if (!files.length)
                return;
            // 压缩
            const sizesMap = new Map();
            for (let index = 0; index < files.length; index++) {
                const filePath = files[index];
                const lodFilePath = resolve(outDir, filePath);
                const engine = /\.svg$/.test(filePath) ? applySVGO : applyTinyfy;
                // 压缩
                const res = await engine(lodFilePath, lodFilePath);
                if (res && res.path) {
                    const stat = await promises.stat(res.path);
                    const path = relative(ROOT, res.path);
                    sizesMap.set(path, {
                        newSize: stat.size / 1024,
                        oldSize: res.size / 1024,
                        percentChange: `${(((stat.size - res.size) * 100) / res.size).toFixed(2)}%`,
                        path: path,
                    });
                }
            }
            if (sizesMap.size == files.length) {
                log(`\n✨ ${cyan('[vite-plugin-tinyfy]')} - optimized images successfully: `);
            }
            else {
                log(`\n✨ ${red('[vite-plugin-tinyfy]')} - errors during optimization: ${bold(greenBright(`${files.length - sizesMap.size}/${files.length}`))}`);
            }
            const keyLengths = Array.from(sizesMap.keys(), (name) => name.length);
            const valueLengths = Array.from(sizesMap.values(), (value) => value.percentChange.length);
            const maxKeyLength = Math.max(...keyLengths);
            const valueKeyLength = Math.max(...valueLengths);
            const colors = shuffleArray([cyan, magenta, grey, blueBright, gray, red, green, yellow, blue]);
            let totalOriginalSize = 0;
            let totalSavedSize = 0;
            let index = 0;
            sizesMap.forEach((value, name) => {
                const { path, percentChange, oldSize, newSize } = value;
                const { dir, base } = parse(path);
                const randomColor = (str) => colors[index](str);
                log(dim(dir + '/') + randomColor(base) +
                    ' '.repeat(2 + maxKeyLength - name.length) +
                    bold(randomColor(`${percentChange} ${' '.repeat(valueKeyLength - percentChange.length)}`)) +
                    ' ' +
                    dim(`${oldSize.toFixed(2)} kB ⭢  ${newSize.toFixed(2)} kB`));
                totalOriginalSize += oldSize;
                totalSavedSize += oldSize - newSize;
                if (index >= (colors.length - 1)) {
                    index = 0;
                }
                else {
                    index++;
                }
            });
            if (totalSavedSize > 0) {
                const savedText = `${totalSavedSize.toFixed(2)}kB`;
                const originalText = `${totalOriginalSize.toFixed(2)}kB`;
                const savingsPercent = `${Math.round((totalSavedSize / totalOriginalSize) * 100)}%`;
                log(`\n💰 total savings = ${green(savedText)}/${green(originalText)} ≈ ${bold(blue(savingsPercent))}`);
            }
        },
    };
};
export default viteTinyfy;
